// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/files/file_util.h"

#if defined(OS_WIN)
#include <io.h>
#endif
#include <stdio.h>

#include <fstream>
#include <limits>

#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"

namespace base {

#if !defined(OS_NACL_NONSFI)
namespace {

    // The maximum number of 'uniquified' files we will try to create.
    // This is used when the filename we're trying to download is already in use,
    // so we create a new unique filename by appending " (nnn)" before the
    // extension, where 1 <= nnn <= kMaxUniqueFiles.
    // Also used by code that cleans up said files.
    static const int kMaxUniqueFiles = 100;

} // namespace

int64_t ComputeDirectorySize(const FilePath& root_path)
{
    int64_t running_size = 0;
    FileEnumerator file_iter(root_path, true, FileEnumerator::FILES);
    while (!file_iter.Next().empty())
        running_size += file_iter.GetInfo().GetSize();
    return running_size;
}

bool Move(const FilePath& from_path, const FilePath& to_path)
{
    if (from_path.ReferencesParent() || to_path.ReferencesParent())
        return false;
    return internal::MoveUnsafe(from_path, to_path);
}

bool ContentsEqual(const FilePath& filename1, const FilePath& filename2)
{
    // We open the file in binary format even if they are text files because
    // we are just comparing that bytes are exactly same in both files and not
    // doing anything smart with text formatting.
    std::ifstream file1(filename1.value().c_str(),
        std::ios::in | std::ios::binary);
    std::ifstream file2(filename2.value().c_str(),
        std::ios::in | std::ios::binary);

    // Even if both files aren't openable (and thus, in some sense, "equal"),
    // any unusable file yields a result of "false".
    if (!file1.is_open() || !file2.is_open())
        return false;

    const int BUFFER_SIZE = 2056;
    char buffer1[BUFFER_SIZE], buffer2[BUFFER_SIZE];
    do {
        file1.read(buffer1, BUFFER_SIZE);
        file2.read(buffer2, BUFFER_SIZE);

        if ((file1.eof() != file2.eof()) || (file1.gcount() != file2.gcount()) || (memcmp(buffer1, buffer2, static_cast<size_t>(file1.gcount())))) {
            file1.close();
            file2.close();
            return false;
        }
    } while (!file1.eof() || !file2.eof());

    file1.close();
    file2.close();
    return true;
}

bool TextContentsEqual(const FilePath& filename1, const FilePath& filename2)
{
    std::ifstream file1(filename1.value().c_str(), std::ios::in);
    std::ifstream file2(filename2.value().c_str(), std::ios::in);

    // Even if both files aren't openable (and thus, in some sense, "equal"),
    // any unusable file yields a result of "false".
    if (!file1.is_open() || !file2.is_open())
        return false;

    do {
        std::string line1, line2;
        getline(file1, line1);
        getline(file2, line2);

        // Check for mismatched EOF states, or any error state.
        if ((file1.eof() != file2.eof()) || file1.bad() || file2.bad()) {
            return false;
        }

        // Trim all '\r' and '\n' characters from the end of the line.
        std::string::size_type end1 = line1.find_last_not_of("\r\n");
        if (end1 == std::string::npos)
            line1.clear();
        else if (end1 + 1 < line1.length())
            line1.erase(end1 + 1);

        std::string::size_type end2 = line2.find_last_not_of("\r\n");
        if (end2 == std::string::npos)
            line2.clear();
        else if (end2 + 1 < line2.length())
            line2.erase(end2 + 1);

        if (line1 != line2)
            return false;
    } while (!file1.eof() || !file2.eof());

    return true;
}
#endif // !defined(OS_NACL_NONSFI)

bool ReadFileToStringWithMaxSize(const FilePath& path,
    std::string* contents,
    size_t max_size)
{
    if (contents)
        contents->clear();
    if (path.ReferencesParent())
        return false;
    FILE* file = OpenFile(path, "rb");
    if (!file) {
        return false;
    }

    const size_t kBufferSize = 1 << 16;
    std::unique_ptr<char[]> buf(new char[kBufferSize]);
    size_t len;
    size_t size = 0;
    bool read_status = true;

    // Many files supplied in |path| have incorrect size (proc files etc).
    // Hence, the file is read sequentially as opposed to a one-shot read.
    while ((len = fread(buf.get(), 1, kBufferSize, file)) > 0) {
        if (contents)
            contents->append(buf.get(), std::min(len, max_size - size));

        if ((max_size - size) < len) {
            read_status = false;
            break;
        }

        size += len;
    }
    read_status = read_status && !ferror(file);
    CloseFile(file);

    return read_status;
}

bool ReadFileToString(const FilePath& path, std::string* contents)
{
    return ReadFileToStringWithMaxSize(path, contents,
        std::numeric_limits<size_t>::max());
}

#if !defined(OS_NACL_NONSFI)
bool IsDirectoryEmpty(const FilePath& dir_path)
{
    FileEnumerator files(dir_path, false,
        FileEnumerator::FILES | FileEnumerator::DIRECTORIES);
    if (files.Next().empty())
        return true;
    return false;
}

FILE* CreateAndOpenTemporaryFile(FilePath* path)
{
    FilePath directory;
    if (!GetTempDir(&directory))
        return NULL;

    return CreateAndOpenTemporaryFileInDir(directory, path);
}

bool CreateDirectory(const FilePath& full_path)
{
    return CreateDirectoryAndGetError(full_path, NULL);
}

bool GetFileSize(const FilePath& file_path, int64_t* file_size)
{
    File::Info info;
    if (!GetFileInfo(file_path, &info))
        return false;
    *file_size = info.size;
    return true;
}

bool TouchFile(const FilePath& path,
    const Time& last_accessed,
    const Time& last_modified)
{
    int flags = File::FLAG_OPEN | File::FLAG_WRITE_ATTRIBUTES;

#if defined(OS_WIN)
    // On Windows, FILE_FLAG_BACKUP_SEMANTICS is needed to open a directory.
    if (DirectoryExists(path))
        flags |= File::FLAG_BACKUP_SEMANTICS;
#endif // OS_WIN

    File file(path, flags);
    if (!file.IsValid())
        return false;

    return file.SetTimes(last_accessed, last_modified);
}
#endif // !defined(OS_NACL_NONSFI)

bool CloseFile(FILE* file)
{
    if (file == NULL)
        return true;
    return fclose(file) == 0;
}

#if !defined(OS_NACL_NONSFI)
bool TruncateFile(FILE* file)
{
    if (file == NULL)
        return false;
    long current_offset = ftell(file);
    if (current_offset == -1)
        return false;
#if defined(OS_WIN)
    int fd = _fileno(file);
    if (_chsize(fd, current_offset) != 0)
        return false;
#else
    int fd = fileno(file);
    if (ftruncate(fd, current_offset) != 0)
        return false;
#endif
    return true;
}

int GetUniquePathNumber(const FilePath& path,
    const FilePath::StringType& suffix)
{
    bool have_suffix = !suffix.empty();
    if (!PathExists(path) && (!have_suffix || !PathExists(FilePath(path.value() + suffix)))) {
        return 0;
    }

    FilePath new_path;
    for (int count = 1; count <= kMaxUniqueFiles; ++count) {
        new_path = path.InsertBeforeExtensionASCII(StringPrintf(" (%d)", count));
        if (!PathExists(new_path) && (!have_suffix || !PathExists(FilePath(new_path.value() + suffix)))) {
            return count;
        }
    }

    return -1;
}
#endif // !defined(OS_NACL_NONSFI)

} // namespace base
